#pragma once

#include <iostream>
#include <algorithm>
#include <memory>
#include <sys/select.h>
#include "Socket.hpp"

using namespace SocketModule;
class selectservice
{
    constexpr static int Size = sizeof(fd_set) * 8; // 1024 * 8
    const static int defaultFd = -1;
public:
    selectservice(uint16_t port)
        :_listensock(std::make_unique<TcpSocket>()),
         _isRunning(false)
    {
        _listensock->BuildTcpSocket(port); 

        // 清空fds_array数组
        for(int i=0; i<Size; i++) {
            _fds_array[i] = defaultFd;
        }

        // 将listensockfd插入到辅助数组的第一个位置
        _fds_array[0] = _listensock->Fd();
    }

    void Strat()
    {
        _isRunning = true;
        while(_isRunning) {
            // 1.此时不要直接accept来获取连接，accept本质上是从listensockfd中读取连接
            // 如果listensockfd读事件没有就绪，就会导致accept阻塞
            // 2.每一次select都必须重置rfds，因为select调用之后，内核可能会修改rfds
            fd_set rfds;
            int maxfd = defaultFd;
            FD_ZERO(&rfds);
            for(int i=0; i<Size; i++) {
                if(_fds_array[i] == defaultFd) continue;
                FD_SET(_fds_array[i], &rfds); //将合法fd设置进rfds中
                maxfd = std::max(maxfd, _fds_array[i]); // 记录下合法fd中最大的fd
            }

            printFd();
            // 3.借助select一次性等待多个文件描述符
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞等待
            // 4.判断select等待结果
            if(n > 0) {
                // 等待成功
                // 此时n表示有几个fd就绪了，由select派发fd
                LOG(loglevel::INFO) << "event-ready... n:" << n;
                Dispath(rfds);
            }
            else if(n == 0) {
                // 超时，或者阻塞等待
                LOG(loglevel::INFO) << "time out...";
            }
            else {
                // 等待失败
                std::cerr << "select error" << std::endl;
                break;
            }
        }
        _isRunning = false;
    }

    // 事件派发器
    void Dispath(fd_set &rfds)
    {
        // 进入派发器，说明一定有文件描述符就绪了
        // 但是文件描述符有多种可能：1.获取链接的； 2.读取数据的； 3.写数据的； 4.异常的
        // 1.先通过遍历fds_array和rfds，判断那些fd的读事件就绪了
        for(int i=0; i<Size; i++) {
            // 1.1 先过滤出合法的fd
            if(_fds_array[i] == defaultFd) continue;

            // 1.2 结合合法的fd，以及rfds里面的内容，来判断那些fd的读事件就绪了
            if(FD_ISSET(_fds_array[i], &rfds)) {
                // 1.3 对于就绪的fd进行分类，判断是用来获取连接的，还是用来获取数据的
                if(_fds_array[i] == _listensock->Fd()) {
                    // 获取链接的
                    Accepter();
                }
                else {
                    // 获取数据的
                    Recver(i);
                }
            }
        }
    }

    // 连接管理器
    void Accepter()
    {
        InetAddr client;
        int sockfd = _listensock->Accept(&client);

        // 将来这个连接也是需要IO的，所以交给select去等待
        int pos = 0;
        for(; pos < Size; pos++) {
            if(_fds_array[pos] == defaultFd) break;
        }
        if(pos == Size) {
            // 说明此时已经没有空位置给该连接了，只能将该连接关掉
            LOG(loglevel::WARINING) << "selectserver full...";
            close(sockfd);
        }
        else {
            // 说明此时还有空位置
            _fds_array[pos] = sockfd;
        }
    }

    // IO处理器
    void Recver(int pos)
    {
        // 到了IO处理器，说明此时该fd的读事件是就绪的，现在直接recv/read不会阻塞
        // 但是直接读取还是有问题的，因为Tcp报文是面向字节流的，我们不知道是否能将一个报文读全
        char buffer[1024] = {0};
        while(true) {
            ssize_t n = recv(_fds_array[pos], buffer, sizeof(buffer) - 1, 0); // 0表示阻塞读取，但这里绝对不会阻塞
            if(n > 0) {
                std::cout << "client say$ " << buffer << std::endl;
            }
            else if(n == 0) {
                // 客户端退出，将该连接关闭，同时从辅助数组中去除
                std::cout << "client quit" << std::endl;
                close(_fds_array[pos]);
                _fds_array[pos] = defaultFd;
                break;
            }
            else {
                std::cerr << "recv error" << std::endl;
                close(_fds_array[pos]);
                _fds_array[pos] = defaultFd;
                break;
            }
        }
    }

    void printFd()
    {
        for(int i=0; i<Size; i++) {
            if(_fds_array[i] == defaultFd) continue;
            std::cout << _fds_array[i] << " ";
        }
        std::cout << std::endl;
    }


private:
    std::unique_ptr<Socket> _listensock;
    bool _isRunning;

    int _fds_array[Size]; // 辅助数组，用于将历史上所有的fd保存起来，方便每一次让select进行等待
};